﻿using SharpDepend;
using SharpDepend.Datatypes;
using SoundEngine3.DspPlugins.Meta;
using System.Collections.Generic;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.Graphics.Components;
using ZquenceStudio3Host.Graphics.GUI;
using System;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls
{
    public class ModulePanel : FramePanel, SoundEngine3.SequenceContent.ITransformSequenceObject
    {
        public static readonly Color COLOR_AUDIO = SharpDepend.Datatypes.Color.Green.Grayed(0.5);
        public static readonly Color COLOR_MIDI = new SharpDepend.Datatypes.Color(43, 191, 255);
        public static readonly Color COLOR_WAVEFORM = SharpDepend.Datatypes.Color.Orange.Grayed(0.5);
        public static readonly Color COLOR_AUDIO_OR_WAVEFORM = SharpDepend.Datatypes.Color.Gray;

        private BaseModulizerUI mNodisizer;
        private Panel header;
        private Panel middle;
        private Label moduleLbl;
        private List<Label> mLabels = new List<Label>();
        private List<Label> mSliderLabels = new List<Label>();

        private Dictionary<ModuleOutput, TexturePanel> mOutputPanels = new Dictionary<ModuleOutput, TexturePanel>();
        private Dictionary<ModuleInput, Panel> mInputPanels = new Dictionary<ModuleInput, Panel>();
        private Dictionary<ParameterProperties, HorizontalSlider> mParameterPanels = new Dictionary<ParameterProperties, HorizontalSlider>();

        Dictionary<ParameterProperties, Label> LabelForParameters = new Dictionary<ParameterProperties, Label>();
        Dictionary<ModuleInput, Label> LabelForInputs = new Dictionary<ModuleInput, Label>();
        Dictionary<ModuleOutput, Label> LabelForOutputs = new Dictionary<ModuleOutput, Label>();

        public Dictionary<ModuleOutput, TexturePanel> ModuleOutputPanels { get; private set; }

        public Dictionary<ModuleInput, Panel> ModuleInputPanels { get; private set; }

        public Dictionary<ParameterProperties, Graphics.Components.Interfaces.IMinMaxValueControl> ModuleParameterSliders { get; private set; }

        public BaseModule Module
        {
            get
            {
                return (BaseModule)Tag;
            }
        }

        public ModulePanel(BaseModulizerUI nodisizer, BaseModule module)
            : base(nodisizer.Gui, true)
        {
            mNodisizer = nodisizer;

            ModuleOutputPanels = new Dictionary<ModuleOutput, TexturePanel>();
            ModuleInputPanels = new Dictionary<ModuleInput, Panel>();
            ModuleParameterSliders = new Dictionary<ParameterProperties, Graphics.Components.Interfaces.IMinMaxValueControl>();

            Touchable = true;
            Intersectable = false;
            SetBorderColor(new SharpDepend.Datatypes.Color(140, 140, 140));
            BackgroundColor = new SharpDepend.Datatypes.Color(70, 70, 70);
            Tag = module;

            middle = new Panel(EngineCore);
            middle.TagName = "middle";
            middle.Touchable = false;
            middle.Intersectable = false;
            Color col = BackgroundColor * 1.3;
            col.A = 255;
            middle.BackgroundColor = col;
            AddComponent(middle);

            header = new Panel(EngineCore);
            header.TagName = "header";
            header.Touchable = true;
            header.Intersectable = false;
            header.Height = 20;
            header.BackgroundColor = new SharpDepend.Datatypes.Color(100, 120, 150);
            AddComponent(header);

            // Display text.
            moduleLbl = new Label(EngineCore);
            moduleLbl.TagName = "moduleLbl";
            moduleLbl.Y = 5;
            moduleLbl.TextSize = header.Height;
            moduleLbl.Height = header.Height;
            moduleLbl.AutoSize = true;
            header.AddComponent(moduleLbl);

            ReBuildComponents();
        }

        private void Clear()
        {
            // TODO: Gjorde en grupp, gick in i den, la till en module, kopplade output till input.
            // Den klagar nu att ModuleParameterSliders redigeras i RemoveParameterPanel() metoden,
            // gör om till en backlängesloop eller något liknande. :)
            foreach (var parameter in mParameterPanels)
            {
                RemoveParameterPanel(parameter.Value as HorizontalSlider);
            }
            foreach (var input in mInputPanels)
            {
                RemoveInputPanel(input.Value);
            }
            foreach (var output in mOutputPanels)
            {
                RemoveOutputPanel(output.Value);
            }
        }

        public void ReBuildComponents()
        {
            Clear();

            BaseModule module = Module;

            // Add parameters as dial buttons.
            IEnumerable<ParameterProperties> parameters = module.Parameters;
            if (module is GroupModule)
            {
                parameters = (module as GroupModule).GetVisibleParameters();
            }
            foreach (ParameterProperties parameter in parameters)
            {
                if (mParameterPanels.ContainsKey(parameter))
                {
                    AddParameterPanel(mParameterPanels[parameter] as HorizontalSlider);
                }
                else
                {
                    CreateParameterPanel(parameter);
                }

                parameter.OnValueChanged += Parameter_OnValueChanged;
            }

            IEnumerable<ModuleInput> inputs = module.Inputs;
            if (module is GroupModule)
            {
                inputs = (module as GroupModule).GetVisibleInputs();
            }
            foreach (var input in inputs)
            {
                if (mInputPanels.ContainsKey(input))
                {
                    AddInputPanel(mInputPanels[input]);
                }
                else
                {
                    CreateInputPanel(input);
                }
            }

            IEnumerable<ModuleOutput> outputs = module.Outputs;
            if (module is GroupModule)
            {
                outputs = (module as GroupModule).GetVisibleOutputs();
            }
            // Loop all outputs for that module.
            foreach (var output in outputs)
            {
                if (mOutputPanels.ContainsKey(output))
                {
                    AddOutputPanel(mOutputPanels[output]);
                }
                else
                {
                    CreateOutputPanel(output);
                }
            }

            RefreshLabels();
        }

        private void Parameter_OnValueChanged(ParameterProperties obj)
        {
            mParameterPanels[obj].Value = obj.RawValue;
        }

        private void CreateParameterPanel(ParameterProperties parameter)
        {
            HorizontalSlider slider = new HorizontalSlider(EngineCore);
            slider.Tag = parameter;

            // Display text.
            Label lbl = new Label(EngineCore);
            lbl.AutoSize = true;
            lbl.Text = parameter.Label;
            AddComponent(lbl);
            mLabels.Add(lbl);
            mSliderLabels.Add(lbl);

            LabelForParameters.Add(parameter, lbl);

            if (mParameterPanels.ContainsKey(parameter))
            {
                throw new AlreadyExistException("Parameter panel");
            }
            mParameterPanels.Add(parameter, slider);

            AddComponent(slider);

            AddParameterPanel(slider);
        }

        private void AddParameterPanel(HorizontalSlider slider)
        {
            ParameterProperties parameter = slider.Tag as ParameterProperties;
            slider.MinValue = parameter.MinValue;
            slider.MaxValue = parameter.MaxValue;
            slider.Tag = parameter;

            slider.Visible = true;
            ModuleParameterSliders.Add(parameter, slider);
        }

        private void RemoveParameterPanel(HorizontalSlider slider)
        {
            slider.Visible = false;
            ModuleParameterSliders.Remove(slider.Tag as ParameterProperties);
        }
        
        private void CreateInputPanel(ModuleInput input)
        {
            FramePanel inputPanel = new FramePanel(EngineCore);
            inputPanel.BorderSize = 2;
            inputPanel.BackgroundColor = Color.Black;

            // Display text.
            Label lbl = new Label(EngineCore);
            inputPanel.Tag = input;
            inputPanel.AddComponent(lbl);
            mLabels.Add(lbl);

            LabelForInputs.Add(input, lbl);

            inputPanel.Touchable = true;
            inputPanel.Intersectable = false;
            inputPanel.SetBorderColor(BaseModulizerUI.GetColor(input));
            inputPanel.Width = 40;
            inputPanel.X = 100 - inputPanel.Height;
            AddComponent(inputPanel);

            if (mInputPanels.ContainsKey(input))
            {
                throw new AlreadyExistException("Input panel");
            }
            mInputPanels.Add(input, inputPanel);

            AddInputPanel(inputPanel);
        }

        private void AddInputPanel(Panel inputPanel)
        {
            ModuleInput input = inputPanel.Tag as ModuleInput;

            inputPanel.Visible = true;
            ModuleInputPanels.Add(input, inputPanel);
        }

        private void RemoveInputPanel(Panel inputPanel)
        {
            ModuleInput input = inputPanel.Tag as ModuleInput;

            inputPanel.Visible = false;
            ModuleInputPanels.Remove(input);
        }

        class OutputTexturePanel : TexturePanel
        {
            private FramePanel mComposite;

            public OutputTexturePanel(BasicGuiManager engineCore, int defaultTextureWidth, int defaultTextureHeight, bool isPart)
                : base(engineCore, defaultTextureWidth, defaultTextureHeight, isPart)
            {
                mComposite = new FramePanel(engineCore, true);
                mComposite.BackgroundColor = Color.Transparent;
                mComposite.BorderSize = 2;
                mComposite.DimensionInProcent = true;
                mComposite.Width = 100;
                mComposite.Height = 100;
                AddPart(mComposite);
            }

            internal void SetBorderColor(Color color)
            {
                mComposite.SetBorderColor(color);
                BackgroundColor = color;
            }
        }

        private void CreateOutputPanel(ModuleOutput output)
        {
            OutputTexturePanel outputPanel = new OutputTexturePanel(EngineCore, 40, 40, true);
            outputPanel.TagName = "output";
            outputPanel.SetBorderColor(BaseModulizerUI.GetColor(output));
            outputPanel.Touchable = true;
            outputPanel.Intersectable = false;
            outputPanel.Width = 40;
            outputPanel.X = -100;
            outputPanel.Tag = output;
            AddComponent(outputPanel);

            // Display text.
            Label lbl = new Label(EngineCore);
            lbl.RightToLeft = true;
            outputPanel.AddComponent(lbl);
            mLabels.Add(lbl);

            LabelForOutputs.Add(output, lbl);

            if (mOutputPanels.ContainsKey(output))
            {
                throw new AlreadyExistException("Output panel");
            }
            mOutputPanels.Add(output, outputPanel);

            AddOutputPanel(outputPanel);
        }

        private void AddOutputPanel(TexturePanel outputPanel)
        {
            ModuleOutput output = outputPanel.Tag as ModuleOutput;

            outputPanel.Visible = true;
            ModuleOutputPanels.Add(output, outputPanel);
        }

        private void RemoveOutputPanel(TexturePanel outputPanel)
        {
            ModuleOutput output = outputPanel.Tag as ModuleOutput;

            outputPanel.Visible = false;
            ModuleOutputPanels.Remove(output);
        }
        
        public override void Render(IGraphicsDevice canvas, Parent parent)
        {
            // Set positions.
            SetBounds();

            // Set header bound.
            header.Width = Width;

            // Zoom for slider bounds.
            double zoom = System.Math.Min(mNodisizer.mGrid.PinchArea.Scale.X, mNodisizer.mGrid.PinchArea.Scale.Y) * 0.125;
            zoom = zoom / (zoom * 1.0 + 1);

            // Zoom for alpha.
            double zoom2 = System.Math.Min(mNodisizer.mGrid.PinchArea.Scale.X, mNodisizer.mGrid.PinchArea.Scale.Y) - 0.5;
            Color white = Color.White;
            if (zoom2 < 0.0)
            {
                zoom2 = 0.0;
            }
            double alpha = System.Math.Min(255, zoom2 * 100);
            white.A = (byte)alpha;

            double panelWidth = System.Math.Min(90, Width * 0.25);
            double SliderHeighOffset = 80;

            // Set middle position.
            middle.Height = Height;
            middle.X = panelWidth;
            middle.Width = Width - (panelWidth * 2);

            // Set slider bounds and alpha.
            int sliderPos = 0;
            foreach (var item in ModuleParameterSliders)
            {
                HorizontalSlider slider = (HorizontalSlider)item.Value;

                slider.X = panelWidth;
                slider.Width = Width - (panelWidth * 2);
                slider.Height = SliderHeighOffset * zoom;
                slider.Y = slider.Height * sliderPos * 2 + slider.Height * 2;
                slider.Alpha = alpha / 255.0;

                ++sliderPos;
            }

            // Set middle alpha.
            Color middleColor = middle.BackgroundColor;
            middleColor.A = (byte)(alpha);
            middle.BackgroundColor = middleColor;

            sliderPos = 0;
            foreach (var label in mSliderLabels)
            {
                label.X = panelWidth;
                label.Width = Width - (panelWidth * 2);
                label.Height = 60 * zoom;
                label.Y = (SliderHeighOffset * zoom) * sliderPos * 2 + (20 * zoom) + label.Height;
                label.TextColor = new Color((int)alpha, 255, 255, 255);

                ++sliderPos;
            }
            
            // Label alpha.
            for (int i = mLabels.Count - 1; i >= 0; i--)
            {
                mLabels[i].TextColor = white;
            }

            base.Render(canvas, parent);
        }

        private void SetBounds()
        {
            double moduleHeight = Height;
            int inputCount = ModuleInputPanels.Count + 1;
            double inputHeight = moduleHeight / inputCount;
            double inputHeightLimit = System.Math.Min(65, moduleHeight / inputCount);

            double panelWidth = System.Math.Min(90, Width * 0.25);
            
            int i = 0;
            foreach (var moduleInput in ModuleInputPanels)
            {
                Panel panel = moduleInput.Value;
                panel.Width = panelWidth;
                panel.X = 0;
                panel.Y = inputHeight * i++ + inputHeightLimit;
                panel.Height = inputHeightLimit * 0.9;
            }

            int outputCount = ModuleOutputPanels.Count + 1;
            double outputHeight = moduleHeight / outputCount;
            double outputHeightLimit = System.Math.Min(65, moduleHeight / outputCount);
            i = 0;
            foreach (var moduleOutput in ModuleOutputPanels)
            {
                Panel panel = moduleOutput.Value;
                panel.X = Width - panelWidth;
                panel.Y = outputHeight * i++ + outputHeightLimit;
                panel.Width = panelWidth;
                panel.Height = outputHeightLimit * 0.9;
            }
        }

        public double DisplayLength
        {
            get { return Module.GuiPosition.Width; }
            set
            {
                Module.GuiPosition.Width = value;
            }
        }

        public double HeightPosition { get { return Module.GuiPosition.Y; } set { Module.GuiPosition.Y = value; } }

        public double Length
        {
            get { return Module.GuiPosition.Width; }
            set
            {
                Module.GuiPosition.Width = value;
            }
        }

        public double Start { get { return Module.GuiPosition.X; } set { Module.GuiPosition.X = value; } }

        public double DisplayHeight
        {
            get { return Module.GuiPosition.Height; }
            set
            {
                Module.GuiPosition.Height = value;
            }
        }

        public void Removed()
        {
            foreach (var parameter in mParameterPanels.Keys)
            {
                parameter.OnValueChanged -= Parameter_OnValueChanged;
            }
        }

        public override string ToString()
        {
            return Module.ToString();
        }

        public void UpdateColors()
        {
            foreach (FramePanel inputPanel in mInputPanels.Values)
            {
                ModuleInput input = inputPanel.Tag as ModuleInput;
                inputPanel.SetBorderColor(BaseModulizerUI.GetColor(input));
            }
            foreach (OutputTexturePanel outputPanel in mOutputPanels.Values)
            {
                ModuleOutput output = outputPanel.Tag as ModuleOutput;
                outputPanel.SetBorderColor(BaseModulizerUI.GetColor(output));
            }
        }
        
        /// <summary>
        /// Updating all labels for this module panel.
        /// </summary>
        public void RefreshLabels()
        {
            //moduleLbl.Text = Module.ID + ": " + Module.Name;
            moduleLbl.Text = Module.Name;

            foreach (var item in LabelForParameters)
            {
                item.Value.Text = item.Key.Label;
            }
            foreach (var item in LabelForInputs)
            {
                item.Value.Text = item.Key.ShortName;
            }
            foreach (var item in LabelForOutputs)
            {
                item.Value.Text = item.Key.ShortName;
            }
        }
    }
}
